package hn.sigit.interop.ws;

import hn.sigit.dao.hnd.administrative.HND_TransactionDAO;
import hn.sigit.dao.hnd.ladmshadow.ParcelDAO;
import hn.sigit.interop.gmlproducers.TransactionGMLProducer;
import hn.sigit.interop.ws.xml.transactionlist.TransactionList;
import hn.sigit.interop.ws.xml.transactionlist.TransactionList.Transaction;
import hn.sigit.interop.ws.xml.transactionresponse.TransactionResponse;
import hn.sigit.interop.ws.xml.transactionresponse.TransactionResponse.ApproverData;
import hn.sigit.interop.ws.xml.transactionresponse.TransactionResponse.Details.ParcelData;
import hn.sigit.logic.general.GeneralHelper;
import hn.sigit.logic.general.ResourceBundleHelper;
import hn.sigit.logic.workflow.transaction.TransactionHelper;
import hn.sigit.model.hnd.administrative.HND_ActivityType;
import hn.sigit.model.hnd.administrative.HND_Transaction;
import hn.sigit.model.hnd.ladmshadow.Parcel;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.StringReader;
import java.io.StringWriter;
import java.util.List;

import javax.ejb.Stateless;
import javax.jws.WebMethod;
import javax.jws.WebParam;
import javax.jws.WebService;
import javax.jws.soap.SOAPBinding;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.soap.SOAPException;

import org.jboss.seam.Component;
import org.jboss.seam.bpm.BusinessProcess;
import org.jboss.seam.bpm.ProcessInstance;
import org.jboss.wsf.spi.annotation.WebContext;


@Stateless
@WebContext(contextRoot="/sigit-services", urlPattern="/transaction")
@SOAPBinding(style=SOAPBinding.Style.RPC)
@WebService(targetNamespace="http://ws.sigit.hn/")
public class SigitTransactionWS {
	private static final String PRESENTATION_ID_SEPARATOR1 = ":";
	private static final String PRESENTATION_ID_SEPARATOR2 = "-";
	
	//TODO: Esta constante es temporal
	private static final String JURISDICTION_CODE = "0503";
	
	private TransactionHelper transactionHelper;
	
	private GeneralHelper generalHelper =
			(GeneralHelper) Component.getInstance(GeneralHelper.NAME, true);
	
	private ResourceBundleHelper resBundle =
			(ResourceBundleHelper) Component.getInstance(ResourceBundleHelper.NAME, true);

	
	@WebMethod
	public String getTransactionData(
			@WebParam(name="presentationId") String presentationId) throws SOAPException {
		return getTrxDataXML(presentationId, false);
	}
	
	@WebMethod
	public String receiveTransaction(
			@WebParam(name="presentationId") String presentationId) throws SOAPException {
		return getTrxDataXML(presentationId, true);
	}

	@WebMethod
	public String getNextTransaction(
			@WebParam(name="idOnly") boolean idOnly) throws SOAPException {
		HND_Transaction trx = HND_TransactionDAO.loadNextRequest();

		if (trx == null)
			throw new SOAPException(resBundle.loadMessage("ws.no_next_deliver_for_external_approval"));
		
		//TODO: Establecer el codigo de municipalidad correcto
		String response;
		if (idOnly)
			response = String.format("%s-%s", JURISDICTION_CODE, trx.getPresentationId());
		else
			response = getTrxDataXML(trx.getPresentationId().toString(), false);
		
		return response;
	}
	
	@WebMethod
	public String getPendingTransactions(
			@WebParam(name="returnCSVList") boolean returnCSVList) throws SOAPException {
		List<HND_Transaction> transactionList = HND_TransactionDAO
		.loadRequestsByCurrentActivity(HND_ActivityType.DELIVER_FOR_EXTERNAL_APPROVAL,
				HND_ActivityType.EXTERNAL_APPROVAL);
		
		try {
			return returnCSVList ? pendingTrxCSV(transactionList) : pendingTrxXml(transactionList);
		}
		catch (JAXBException e) {
			throw new SOAPException(
					resBundle.loadMessage("ws.could_not_produce_xml"), e);
		}
	}
	
	@WebMethod
	public String getPendingForDeliveryTransactions(
			@WebParam(name="returnCSVList") boolean returnCSVList) throws SOAPException {
		List<HND_Transaction> transactionList = HND_TransactionDAO
		.loadRequestsByCurrentActivity(HND_ActivityType.DELIVER_FOR_EXTERNAL_APPROVAL);
		
		try {
			return returnCSVList ? pendingTrxCSV(transactionList) : pendingTrxXml(transactionList);
		}
		catch (JAXBException e) {
			throw new SOAPException(
					resBundle.loadMessage("ws.could_not_produce_xml"), e);
		}
	}

	@WebMethod
	public String processTransactionResponse(
			@WebParam(name="xmlResponsePayload") String xmlResponsePayload) throws SOAPException {
		TransactionResponse trxResponse = null;
		
		try {
			JAXBContext context = JAXBContext.newInstance(TransactionResponse.class);
			trxResponse = (TransactionResponse) context.createUnmarshaller()
			.unmarshal(new StringReader(xmlResponsePayload));
		} catch (JAXBException e) {
			e.printStackTrace();
			String msg = resBundle.loadMessage("ws.bad_transaction_response_xml"); 
			throw new SOAPException(msg
					.replaceAll("\\{0\\}",
							e.getLinkedException() != null ? e.getLinkedException().getLocalizedMessage() : ""));
		}

		String presentationId = trxResponse.getPresentationId();
		String municipalCode = null;
		Long presentationNo = null;
		
		try {
			if (presentationId.contains(PRESENTATION_ID_SEPARATOR1)) {
				//TODO: Hacer Forward al servicio web de la municipalidad especifica
				String[] strs = presentationId.split(PRESENTATION_ID_SEPARATOR1);
				municipalCode = strs[0];
				presentationNo = Long.valueOf(strs[1]);
			}
			else if (presentationId.contains(PRESENTATION_ID_SEPARATOR2)) {
				//TODO: Hacer Forward al servicio web de la municipalidad especifica
				String[] strs = presentationId.split(PRESENTATION_ID_SEPARATOR2);
				municipalCode = strs[0];
				presentationNo = Long.valueOf(strs[1]);
			}
			else {
				presentationNo = Long.valueOf(presentationId);
			
				//HND_Transaction trx = HND_TransactionDAO.loadRequestByPresentationId(presentationNo);
				transactionHelper = (TransactionHelper) Component.getInstance("transactionHelper");
				transactionHelper.setTransactionId(presentationNo);
				if (transactionHelper.getTransaction().getCurrentActivity() == HND_ActivityType.EXTERNAL_APPROVAL) {
					//proceed only if state is EXTERNAL_APPROVAL
					//STEP 1: Resume the business process
					BusinessProcess.instance().resumeProcess(TransactionHelper.TRANSACTION_PROCESS,
							presentationId.toString());
					
					//STEP 2: Set the cadastral keys
					ApproverData approverData = trxResponse.getApproverData();
					for (ParcelData pd : trxResponse.getDetails().getParcelData()) {
						Parcel parcel = ParcelDAO.loadParcelAfterTransactionByPresentationIdAndSuID(presentationNo, pd.getFeatureId());
						parcel.setCadastralKey(pd.getNewCadastralKey());
						ParcelDAO.save(parcel);
					}
					
					//STEP 3: Copy from shadow schema to front schema and complete transaction data
					transactionHelper.endProcess(trxResponse.isApproved(), xmlResponsePayload);
	
					//STEP 5: Signal the business process. Should end
					try {
						ProcessInstance.instance().signal();
					}
					catch (IllegalStateException e) {
						e.printStackTrace();
					}
				}
				else {
					throw new SOAPException(resBundle.loadMessage("ws.illegal_state"));
				}
			}
		}
		catch (NumberFormatException e) {
			throw new SOAPException(resBundle.loadMessage("ws.bad_transaction_id"));
		}
		
		return "OK";
	}
	
	
	
	private String getTrxDataXML(String presentationId, boolean changeState) throws SOAPException {
		ByteArrayOutputStream baos = new ByteArrayOutputStream(16834);
		
		String municipalCode = "";
		Long presentationNo = null;
		try {
			if (presentationId.contains(PRESENTATION_ID_SEPARATOR1)) {
				String[] msg = presentationId.split(PRESENTATION_ID_SEPARATOR1, 2);
				municipalCode = msg[0];
				presentationNo = Long.valueOf(msg[1]);
			}
			else if (presentationId.contains(PRESENTATION_ID_SEPARATOR2)) {
				String[] msg = presentationId.split(PRESENTATION_ID_SEPARATOR2, 2);
				municipalCode = msg[0];
				presentationNo = Long.valueOf(msg[1]);
			}
			else
				presentationNo = Long.valueOf(presentationId);
		}
		catch (NumberFormatException e) {
			throw new SOAPException(resBundle.loadMessage("ws.bad_transaction_id"));
		}
		
		try {
			HND_Transaction trx = HND_TransactionDAO.loadRequestByPresentationId(presentationNo);
			if (trx == null)
				throw new SOAPException(resBundle.loadMessage("ws.transaction_not_found"));
			
			TransactionGMLProducer trxGMLProducer = new TransactionGMLProducer(generalHelper.getJurisdictionCode(), trx);
			trxGMLProducer.produceXml(baos);
			
			if (changeState && trx.getCurrentActivity() == HND_ActivityType.DELIVER_FOR_EXTERNAL_APPROVAL) {
				try {
					BusinessProcess.instance().resumeProcess(TransactionHelper.TRANSACTION_PROCESS,
							presentationId.toString());
					
					ProcessInstance.instance().signal();
				}
				catch (IllegalStateException e) {
					e.printStackTrace();
				}
			}
		}
		catch (IOException ioe) {
			ioe.printStackTrace();
			throw new SOAPException(resBundle.loadMessage("ws.could_not_produce_xml"));
		}

		return baos.toString();
	}

	private String pendingTrxXml(List<HND_Transaction> transactionList) throws JAXBException {
		TransactionList tl = new TransactionList();
		JAXBContext context = JAXBContext.newInstance(TransactionList.class);
		for (HND_Transaction hndTrx : transactionList) {
			Transaction t = new Transaction();
			t.setPresentationId(String.format("%s-%s",
					generalHelper.getJurisdictionCode(),
					hndTrx.getPresentationId()));
			
			t.setPresentationDate(hndTrx.getPresentationDate().toString());
			t.setCurrentActivity(hndTrx.getCurrentActivity().toString());
			
			tl.getTransaction().add(t);
		}
		
		StringWriter sw = new StringWriter();
		context.createMarshaller().marshal(tl, sw);
		
		return sw.toString();
	}
	private String pendingTrxCSV(List<HND_Transaction> transactionList) {
		StringBuilder sb = new StringBuilder();
		for (HND_Transaction hndTrx : transactionList) {
			sb.append(String.format("%s-%s",
					generalHelper.getJurisdictionCode(),
					hndTrx.getPresentationId()));
			sb.append(',');
		}
		if (sb.length() > 0)
			sb.deleteCharAt(sb.length() - 1);
		
		return sb.toString();
	}
	
}
